package ch.ethz.eai.webservice;

import ch.ethz.eai.data.*;
import ch.ethz.eai.facade.*;

public class OurWebService 
{
	// This is where we store locally the weather information that has 
	// been uploaded by the users of our service
	private StatusStore weatherDB;
	
	// The radius from our location within which we are going to retrieve
	// the weather status reported by the users
	private double radius = 0.1;
	
	public OurWebService()
	{
		super();
		this.weatherDB = new StatusStore();
	}
	
	private Location convertStringToLocationObject (String location)
	{
		// We assume that the initial string is in a comma delimited
		// format as in the following example:
		// "Raemistrasse 101, 8006, Zurich, Switzerland"
		// We assume that the client is responsible for sending a string
		// with the correct number of fields, even if some of them are empty 
		// as in the following example:
		// "Raemistrasse 101,, Zurich, Switzerland"
		
		Location locobj = null;
		
		try
		{
			String delims = "[,]";
			String[] tokens = location.split(delims);
			locobj = new Location(tokens[0], tokens[2], tokens[1], tokens[3]);
			// Constructor Syntax: Location(streetAddress, town, postalCode, country)
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
		
		return locobj;
	}
	
	
	// The following two methods are depreciated. They were used in the initial 
	// implementation of our web service in which we returned the results as a 
	// single string
	
	/*
	
	private String convertLocationListToString (Location[] loclist)
	{
		// We convert a list of Location objects to a string that contains a list
		// of the corresponding addresses separated by the character "|".
		// Within each address, the various fields are separated by the character ","
		// e.g: "Raemistrasse 101, 8006, Zurich, Switzerland | Bahnhofstrasse 12, 8001, Zurich, Switzerland"
		
		String res = "";
		Location loc;
		
		for(int i=0; i < loclist.length; i++)
		{
			loc = loclist[i];
			res += loc.getStreetAddress() + "," + loc.getPostalCode() + "," + loc.getTown() + "," + loc.getCountry() + "|";
		}
		
		// Remove the last "|" delimiter that was added
		if (res.length() > 0)
		{
			res = res.substring(0,res.length()-1);
		}
		
		return res;
	}
	
	private String convertPOIListToString (PointOfInterest[] poilist)
	{
		// We convert a list of Points of Interest to a string that contains a list
		// of the corresponding names and addresses separated by the character "|".
		// Within each data entry, the various fields are separated by the character ","
		// e.g: "ETH, Raemistrasse 101, 8006, Zurich, Switzerland | Hotel Limmat, Bahnhofstrasse 12, 8001, Zurich, Switzerland"
		
		String res = "";
		String name;
		Location loc;
		
		for(int i=0; i < poilist.length; i++)
		{
			name = poilist[i].getName();
			loc  = poilist[i].getLocation();
			res += name + "," + loc.getStreetAddress() + "," + loc.getPostalCode() + "," + loc.getTown() + "," + loc.getCountry() + "|";
		}
		
		// Remove the last "|" delimiter that was added
		if (res.length() > 0)
		{
			res = res.substring(0,res.length()-1);
		}
		
		return res;
	}
	
	*/
	
	private String[] convertLocationListToStringArray (Location[] loclist)
	{
		// We convert a list of Location objects to a string that contains a list
		// of the corresponding addresses separated by the character "|".
		// Within each address, the various fields are separated by the character ","
		// e.g: "Raemistrasse 101, 8006, Zurich, Switzerland | Bahnhofstrasse 12, 8001, Zurich, Switzerland"
		
		String[] res = new String[loclist.length];
		Location loc;
		
		for(int i=0; i < loclist.length; i++)
		{
			loc = loclist[i];
			res[i] = loc.getStreetAddress() + "," + loc.getPostalCode() + "," + loc.getTown() + "," + loc.getCountry();
		}
			
		return res;
	}
	
	private String[] convertPOIListToStringArray (PointOfInterest[] poilist)
	{
		// We convert a list of Points of Interest to a string that contains a list
		// of the corresponding names and addresses separated by the character "|".
		// Within each data entry, the various fields are separated by the character ","
		// e.g: "ETH, Raemistrasse 101, 8006, Zurich, Switzerland | Hotel Limmat, Bahnhofstrasse 12, 8001, Zurich, Switzerland"
		
		String[] res = new String[poilist.length];
		String name;
		Location loc;
		
		for(int i=0; i < poilist.length; i++)
		{
			name = poilist[i].getName();
			loc  = poilist[i].getLocation();
			res[i] = name + "," + loc.getStreetAddress() + "," + loc.getPostalCode() + "," + loc.getTown() + "," + loc.getCountry();
		}
		
		return res;
	}
	
	public ResponseMessage setWeatherStatusByAddress (String stype, String location)
	{
		ResponseMessage rm = null;
		
		try
		{
			// Convert the weather type from string to integer
			int type;
			type = Integer.parseInt(stype);
			
			// Convert the location string to a Location object
			Location loc = convertStringToLocationObject (location);
			
			// Convert the initial Location object that didn't have any coordinates 
			// to a list of matching Location objects that contain coordinates and  
			// are returned by the geolocation web service that we use
			GeocodeFacade gcf = new GeocodeFacade();
			Location[] loclist =  gcf.geocode(loc);

			if (loclist.length == 0)
			{
				// If the list is empty return an error code
				rm = new ResponseMessage(ResponseMessage.FAILURE, "Geocoding returned no matching location");
			}
			else if (loclist.length == 1)
			{
				// If the list contains only one Location object, we save the weather 
				// status for this location and return a success code
				WeatherStatus ws = new WeatherStatus(type, loclist[0]);
				Boolean status = weatherDB.save(ws);
				
				if(status)
				{
					rm = new ResponseMessage(ResponseMessage.SUCCESS, "Weather status saved successfully");
				}
				else
				{
					rm = new ResponseMessage(ResponseMessage.FAILURE, "Problem with updating the database although geocoding returned one matching location");
				}
			}
			else
			{
				// If the list contains more than one Location objects we:
				// - do not save the weather status
				// - return an error code
				// - return a list of possible addresses as a suggestion to the user
				
				String[] addresslist = convertLocationListToStringArray(loclist);
				rm = new ResponseMessage(ResponseMessage.ADDITIONAL_ACTION_NEEDED, "More that one matching locations found", addresslist);
			}
		}
		catch (NumberFormatException ne)
		{
			rm = new ResponseMessage(ResponseMessage.FAILURE, "Converting weather type from string to integer failed");
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}

		return rm;
	}
	
	public ResponseMessage setWeatherStatusByCoordinates (String stype, double longtitude, double latitude)
	{
		ResponseMessage rm = null;

		try
		{
			// Convert the weather type from string to integer
			int type;
			type = Integer.parseInt(stype);
			
			// Convert the location coordinates to a Location object
			Location loc = new Location (longtitude, latitude);
			
			// Convert the initial Location object that didn't have any address to a 
			// list of matching Location objects that contain the corresponding addresses 
			// and are returned by the inverse geolocation web service that we use
			GeocodeFacade gcf = new GeocodeFacade();
			Location[] loclist =  gcf.reverseGeocode(loc);	
			
			if (loclist.length == 0)
			{
				// If the list is empty return an error code
				rm = new ResponseMessage(ResponseMessage.FAILURE, "Geocoding returned no matching location");
			}
			else if (loclist.length == 1)
			{
				// If the list contains only one Location object, we save the weather 
				// status for this location and return a success code
				WeatherStatus ws = new WeatherStatus(type, loclist[0]);
				Boolean status = weatherDB.save(ws);
				
				if(status)
				{
					rm = new ResponseMessage(ResponseMessage.SUCCESS, "Weather status saved successfully");
				}
				else
				{
					rm = new ResponseMessage(ResponseMessage.FAILURE, "Problem with updating the database although geocoding returned one matching location");
				}
			}
			else
			{
				// If the list contains more than one Location objects we:
				// - do not save the weather status
				// - return an error code
				// - return a list of possible addresses as a suggestion to the user
				
				String[] addresslist =  convertLocationListToStringArray(loclist);
				rm = new ResponseMessage(ResponseMessage.ADDITIONAL_ACTION_NEEDED, "More that one matching locations found", addresslist);
			}
		}
		catch (NumberFormatException ne)
		{
			rm = new ResponseMessage(ResponseMessage.FAILURE, "Converting weather type from string to integer failed");
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
		
		return rm;
	}
	
	public ResponseMessage getProposalsByAddress (String location)
	{
		ResponseMessage rm = null;
		
		try
		{
			// Convert the location string to a Location object
			Location loc = convertStringToLocationObject (location);
			
			// Convert the initial Location object that didn't have any coordinates 
			// to a list of matching Location objects that contain coordinates and  
			// are returned by the geolocation web service that we use
			GeocodeFacade gcf = new GeocodeFacade();
			Location[] loclist =  gcf.geocode(loc);

			if (loclist.length == 0)
			{
				// If the list is empty return an error code
				rm = new ResponseMessage(ResponseMessage.FAILURE, "Geocoding returned no matching location");
			}
			else if (loclist.length == 1)
			{
				// Get the official weather status (as reported by the web service that we have chosen)
				loc = loclist[0];
				
				WeatherFacade wf = new WeatherFacade();
				WeatherStatus ows = wf.getWeatherFor(loc);
				
				// Get the unofficial weather status (as reported by the users)
				// The variable votesInFavor represents the number of users that 
				// have submitted the "winning" weather status within our radius
				// The variable totalVotes represents the total number of users
				// that have uploaded a weather report within our radius
				StatusResponse sr = weatherDB.majorityFor(loc,radius);
				WeatherStatus uws = new WeatherStatus(sr.type, loc);
				int votesInFavor  = sr.statusesOfThisType;
				int totalVotes    = sr.totalStatuses;
				
				// Calculate the weighted average of the official and the reported weather 
				// status and decide which type of weather is prevalent in the area
				// - TOTAL_VOTES_THRES represents the minimum number of user entries that
				// we require in order to take the data uploaded by the users into account
				// - PERCENTAGE_THRES represents the minimum percentage of votes that we
				// require in order to take the data uploaded by the users seriously
				int ws = WeatherStatus.STAT_UNKNOWN;
				int TOTAL_VOTES_THRES = 5;
				double PERCENTAGE_THRES = 0.55;
				
				if (ows.getStatus() == uws.getStatus())
				{
					ws = ows.getStatus();
				}
				else
				{
					if (totalVotes < TOTAL_VOTES_THRES)
					{
						// If we have only a few votes we don't take the data uploaded
						// by the users into account
						ws = ows.getStatus();
					}
					else
					{
						if (votesInFavor/totalVotes > PERCENTAGE_THRES)
						{
							// If the we have a sufficient number of votes and the percentage 
							// of people that have voted in favor of the proposed weather status
							// is larger than a threshold we take into account only the status
							// reported by the users
							ws = uws.getStatus();
						}
						else
						{
							ws = ows.getStatus();
						}
					}
				}
							
				// Get proposals for interesting venues in the area depending on the 
				// previously calculated weather status
				
				boolean flag = false;
				SearchFacade sf = new SearchFacade();
				PointOfInterest[] poilist = null;
				String[] venuelist;
 
				if (ws == WeatherStatus.STAT_CLEAR)
				{
					poilist = sf.getPOIs(loc, PointOfInterest.TYPE_TENNIS);
				}
				else if (ws == WeatherStatus.STAT_CLOUDS)
				{
					poilist = sf.getPOIs(loc, PointOfInterest.TYPE_CINEMA);
				}
				else if (ws == WeatherStatus.STAT_RAIN)
				{
					poilist = sf.getPOIs(loc, PointOfInterest.TYPE_MUSEUM);
				}
				else if (ws == WeatherStatus.STAT_SNOW)
				{
					poilist = sf.getPOIs(loc, PointOfInterest.TYPE_BOWLING);
				}
				else if (ws == WeatherStatus.STAT_STORM)
				{
					poilist = sf.getPOIs(loc, PointOfInterest.TYPE_HOTEL);
				}
				else
				{
					// This is the case when ws == WeatherStatus.STAT_UNKNOWN
					flag = true;
				}
				
				if(flag)
				{
					rm = new ResponseMessage(ResponseMessage.FAILURE, "No weather information available");
				}
				else
				{
					venuelist = convertPOIListToStringArray(poilist);
					String response_status = "";
					
					if (venuelist.length == 0)
					{
						response_status =  "Unfortunatelly, no interesting locations were found in ths area";
					}
					rm = new ResponseMessage(ResponseMessage.SUCCESS, response_status, ws, venuelist);
				}
			}
			else
			{
				// If the list contains more than one Location objects we:
				// - return an error code
				// - return a list of possible addresses as a suggestion to the user
				
				String[] addresslist =  convertLocationListToStringArray(loclist);
				rm = new ResponseMessage(ResponseMessage.ADDITIONAL_ACTION_NEEDED, "More that one matching locations found", addresslist);
			}	
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
					
		return rm;
	}
	
	public ResponseMessage getProposalsByCoordinates (double longtitude, double latitude)
	{
		ResponseMessage rm = null; 
		
		try
		{	
			// Convert the location coordinates to a Location object
			Location loc = new Location (longtitude, latitude);
			
			// Convert the initial Location object that didn't have any address to a 
			// list of matching Location objects that contain the corresponding addresses 
			// and are returned by the inverse geolocation web service that we use
			GeocodeFacade gcf = new GeocodeFacade();
			Location[] loclist =  gcf.reverseGeocode(loc);	
			
			if (loclist.length == 0)
			{
				// If the list is empty return an error code
				rm = new ResponseMessage(ResponseMessage.FAILURE, "Geocoding returned no matching location");
			}
			else if (loclist.length == 1)
			{
				// Get the official weather status (as reported by the web service that we have chosen)
				WeatherFacade wf = new WeatherFacade();
				WeatherStatus ows = wf.getWeatherFor(loc);
				
				// Get the unofficial weather status (as reported by the users)
				// The variable votesInFavor represents the number of users that 
				// have submitted the "winning" weather status within our radius
				// The variable totalVotes represents the total number of users
				// that have uploaded a weather report within our radius
				StatusResponse sr = weatherDB.majorityFor(loc,radius);
				WeatherStatus uws = new WeatherStatus(sr.type, loc);
				int votesInFavor  = sr.statusesOfThisType;
				int totalVotes    = sr.totalStatuses;
				
				// Calculate the weighted average of the official and the reported weather 
				// status and decide which type of weather is prevalent in the area
				// - TOTAL_VOTES_THRES represents the minimum number of user entries that
				// we require in order to take the data uploaded by the users into account
				// - PERCENTAGE_THRES represents the minimum percentage of votes that we
				// require in order to take the data uploaded by the users seriously
				int ws = WeatherStatus.STAT_UNKNOWN;
				int TOTAL_VOTES_THRES = 5;
				double PERCENTAGE_THRES = 0.55;
				
				if (ows.getStatus() == uws.getStatus())
				{
					ws = ows.getStatus();
				}
				else
				{
					if (totalVotes < TOTAL_VOTES_THRES)
					{
						// If we have only a few votes we don't take the data uploaded
						// by the users into account
						ws = ows.getStatus();
					}
					else
					{
						if (votesInFavor/totalVotes > PERCENTAGE_THRES)
						{
							// If the we have a sufficient number of votes and the percentage 
							// of people that have voted in favor of the proposed weather status
							// is larger than a threshold we take into account only the status
							// reported by the users
							ws = uws.getStatus();
						}
						else
						{
							ws = ows.getStatus();
						}
					}
				}
							
				// Get proposals for interesting venues in the area depending on the 
				// previously calculated weather status
				
				boolean flag = false;
				SearchFacade sf = new SearchFacade();
				PointOfInterest[] poilist = null;
				String[] venuelist;
 
				if (ws == WeatherStatus.STAT_CLEAR)
				{
					poilist = sf.getPOIs(loc, PointOfInterest.TYPE_TENNIS);
				}
				else if (ws == WeatherStatus.STAT_CLOUDS)
				{
					poilist = sf.getPOIs(loc, PointOfInterest.TYPE_CINEMA);
				}
				else if (ws == WeatherStatus.STAT_RAIN)
				{
					poilist = sf.getPOIs(loc, PointOfInterest.TYPE_MUSEUM);
				}
				else if (ws == WeatherStatus.STAT_SNOW)
				{
					poilist = sf.getPOIs(loc, PointOfInterest.TYPE_BOWLING);
				}
				else if (ws == WeatherStatus.STAT_STORM)
				{
					poilist = sf.getPOIs(loc, PointOfInterest.TYPE_HOTEL);
				}
				else
				{
					// This is the case when ws == WeatherStatus.STAT_UNKNOWN
					flag = true;
				}
				
				if(flag)
				{
					rm = new ResponseMessage(ResponseMessage.FAILURE, "No weather information available");
				}
				else
				{
					venuelist = convertPOIListToStringArray(poilist);
					String response_status = "";
					
					if (venuelist.length == 0)
					{
						response_status =  "Unfortunatelly, no interesting locations were found in ths area";
					}
					rm = new ResponseMessage(ResponseMessage.SUCCESS, response_status, ws, venuelist);
				}
			}
			else
			{
				// If the list contains more than one Location objects we:
				// - return an error code
				// - return a list of possible addresses as a suggestion to the user
				
				String[] addresslist =  convertLocationListToStringArray(loclist);
				rm = new ResponseMessage(ResponseMessage.ADDITIONAL_ACTION_NEEDED, "More that one matching locations found", addresslist);
			}	
			
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
					
		return rm;
	}

}
